<template>
  <div>
    <div style="display: flex">
      <Input
        v-model="data"
        @on-change="handleChange"
        :placeholder="placeholder"
        :size="size"
        :disabled="disabled"
        :readonly="readonly"
        :maxlength="maxlength"
        :clearable="clearable"
        v-if="showInput"
        style="margin-right: 10px"
      />
      <Button
        :size="size"
        :type="type"
        :shape="shape"
        :ghost="ghost"
        :disabled="disabled"
        :icon="icon"
        @click="handleShow"
        >{{ text }}</Button
      >
    </div>
    <Modal
      v-model="showModal"
      :mask-closable="false"
      :width="width"
      :fullscreen="full"
      @on-visible-change="changeModal"
      class="map-modal"
    >
      <div slot="header">
        <div class="ivu-modal-header-inner">{{ text }}</div>
        <a @click="handleFull" class="modal-fullscreen">
          <Icon
            v-show="!full"
            type="ios-expand"
            class="model-fullscreen-icon"
          />
          <Icon
            v-show="full"
            type="ios-contract"
            class="model-fullscreen-icon"
          />
        </a>
        <a @click="showModal = false" class="ivu-modal-close">
          <Icon type="ios-close" class="ivu-icon-ios-close" />
        </a>
      </div>
      <div :id="id" :style="{ width: '100%', height: modalHeight }"></div>
      <div slot="footer">
        <Row align="middle" justify="space-between">
          <Alert show-icon style="margin-bottom: 0" v-show="currentValue"
            >当前坐标：{{ currentValue }}</Alert
          >
          <div v-show="!currentValue"></div>
          <div>
            <Button type="text" @click="showModal = false" v-if="!preview"
              >取消</Button
            >
            <Button @click="showModal = false" v-if="preview">关闭</Button>
            <Button type="primary" @click="handelSubmit" v-if="!preview"
              >确认提交</Button
            >
          </div>
        </Row>
      </div>
    </Modal>
  </div>
</template>
<script>
import mapboxgl from "mapbox-gl";
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
export default {
  name: "map",
  props: {
    id: {
      type: String,
      default: "map",
    },
    value: {
      type: String,
      default: "",
    },
    showInput: {
      type: Boolean,
      default: true,
    },
    preview: {
      type: Boolean,
      default: false,
    },
    text: {
      type: String,
      default: "地图选点",
    },
    width: {
      type: Number,
      default: 900,
    },
    pitch: {
      type: Number,
      default: 0,
    },
    decimal: {
      type: Number,
      default: 6,
    },
    draggable: {
      type: Boolean,
      default: true,
    },
    size: String,
    type: String,
    shape: String,
    ghost: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    clearable: {
      type: Boolean,
      default: true,
    },
    placeholder: {
      type: String,
      default: "输入坐标或选择地点",
    },
    maxlength: Number,
    readonly: {
      type: Boolean,
      default: false,
    },
    icon: {
      type: String,
      default: "md-locate",
    },
    style: {
      type: String,
      default: "mapbox://styles/mapbox/streets-v11",
    },
    center: {
      type: Array,
      default: [116.35, 39.85],
    },
    zoom: {
      type: Number,
      default: 9,
    },
    compact: {
      type: Boolean,
      default: true,
    },
    customAttribution: {
      type: String,
      default: "",
    },
    searchable: {
      type: Boolean,
      default: true,
    },
    changeStyle: {
      type: Boolean,
      default: true,
    },
    navigation: {
      type: Boolean,
      default: true,
    },
    locate: {
      type: Boolean,
      default: true,
    },
    fullscreen: {
      type: Boolean,
      default: false,
    },
    building3D: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      data: this.value,
      mapbox: null,
      currentValue: "",
      showModal: false,
      full: false,
      modalHeight: "500px",
      marker: null,
    };
  },
  methods: {
    init() {
      if (!config.mapboxToken) {
        return;
      }
      mapboxgl.accessToken = config.mapboxToken;
      this.mapbox = new mapboxgl.Map({
        container: this.id,
        style: this.style,
        center: this.center,
        zoom: this.zoom,
        pitch: this.pitch,
        attributionControl: false,
      });
      // 属性
      this.mapbox.addControl(
        new mapboxgl.AttributionControl({
          compact: this.compact,
          customAttribution: this.customAttribution,
        })
      );
      // 搜索
      if (this.searchable) {
        this.mapbox.addControl(
          new MapboxGeocoder({
            accessToken: mapboxgl.accessToken,
            mapboxgl: mapboxgl,
          })
        );
      }
      // 地图样式切换
      class ChangeStyleControl {
        getDefaultPosition() {
          const defaultPosition = "top-right";
          return defaultPosition;
        }
        onAdd(map) {
          this.map = map;
          this.controlContainer = document.createElement("div");
          this.controlContainer.classList.add("mapboxgl-ctrl");
          this.controlContainer.classList.add("mapboxgl-ctrl-group");
          this.mapStyleContainer = document.createElement("div");
          this.styleButton = document.createElement("button");
          this.styleButton.type = "button";
          this.mapStyleContainer.classList.add("mapboxgl-style-list");
          const defaultStyle = "街道地图";
          const styles = [
            { title: "街道地图", uri: "mapbox://styles/mapbox/streets-v11" },
            {
              title: "卫星街道",
              uri: "mapbox://styles/mapbox/satellite-streets-v11",
            },
            { title: "暗黑风格", uri: "mapbox://styles/mapbox/dark-v10" },
            { title: "明亮风格", uri: "mapbox://styles/mapbox/light-v10" },
            { title: "户外地图", uri: "mapbox://styles/mapbox/outdoors-v11" },
            { title: "卫星地图", uri: "mapbox://styles/mapbox/satellite-v9" },
          ];
          for (const style of styles) {
            const styleElement = document.createElement("button");
            styleElement.type = "button";
            styleElement.innerText = style.title;
            styleElement.classList.add(
              style.title.replace(/[^a-z0-9-]/gi, "_")
            );
            styleElement.dataset.uri = JSON.stringify(style.uri);
            styleElement.addEventListener("click", (event) => {
              const srcElement = event.srcElement;
              if (srcElement.classList.contains("active")) {
                return;
              }
              this.map.setStyle(JSON.parse(srcElement.dataset.uri));
              this.map.on("data", (e) => {
                if (e.isSourceLoaded) {
                  const labelList = this.map
                    .getStyle()
                    .layers.filter((layer) => {
                      return /-label/.test(layer.id);
                    });
                  for (let labelLayer of labelList) {
                    this.map.setLayoutProperty(labelLayer.id, "text-field", [
                      "coalesce",
                      ["get", "name_zh-Hans"],
                      ["get", "name"],
                    ]);
                  }
                }
              });
              this.mapStyleContainer.style.display = "none";
              this.styleButton.style.display = "block";
              const elms = this.mapStyleContainer.getElementsByClassName(
                "active"
              );
              while (elms[0]) {
                elms[0].classList.remove("active");
              }
              srcElement.classList.add("active");
            });
            if (style.title === defaultStyle) {
              styleElement.classList.add("active");
            }
            this.mapStyleContainer.appendChild(styleElement);
          }
          this.styleButton.classList.add("mapboxgl-ctrl-icon");
          this.styleButton.classList.add("mapboxgl-style-switcher");
          this.styleButton.addEventListener("click", () => {
            this.styleButton.style.display = "none";
            this.mapStyleContainer.style.display = "block";
          });
          document.addEventListener("click", this.onDocumentClick);
          this.controlContainer.appendChild(this.styleButton);
          this.controlContainer.appendChild(this.mapStyleContainer);
          return this.controlContainer;
        }
        onRemove() {
          if (
            !this.controlContainer ||
            !this.controlContainer.parentNode ||
            !this.map ||
            !this.styleButton
          ) {
            return;
          }
          this.styleButton.removeEventListener("click", this.onDocumentClick);
          this.controlContainer.parentNode.removeChild(this.controlContainer);
          document.removeEventListener("click", this.onDocumentClick);
          this.map = undefined;
        }
        onDocumentClick(event) {
          if (
            this.controlContainer &&
            !this.controlContainer.contains(event.target) &&
            this.mapStyleContainer &&
            this.styleButton
          ) {
            this.mapStyleContainer.style.display = "none";
            this.styleButton.style.display = "block";
          }
        }
      }
      if (this.changeStyle) {
        this.mapbox.addControl(new ChangeStyleControl());
      }
      // 导航控件
      if (this.navigation) {
        this.mapbox.addControl(new mapboxgl.NavigationControl());
      }
      // 定位
      if (this.locate) {
        this.mapbox.addControl(
          new mapboxgl.GeolocateControl({
            positionOptions: {
              enableHighAccuracy: true,
            },
            trackUserLocation: true,
          })
        );
      }
      // 全屏
      if (this.fullscreen) {
        this.mapbox.addControl(
          new mapboxgl.FullscreenControl({
            container: document.querySelector("body"),
          })
        );
      }
      this.mapbox.on("load", () => {
        // 中文
        const labelList = this.mapbox.getStyle().layers.filter((layer) => {
          return /-label/.test(layer.id);
        });
        for (let labelLayer of labelList) {
          this.mapbox.setLayoutProperty(labelLayer.id, "text-field", [
            "coalesce",
            ["get", "name_zh-Hans"],
            ["get", "name"],
          ]);
        }
        // 3D建筑
        if (this.building3D) {
          this.mapbox.addLayer({
            id: "3d-buildings",
            source: "composite",
            "source-layer": "building",
            filter: ["==", "extrude", "true"],
            type: "fill-extrusion",
            minzoom: 15,
            paint: {
              "fill-extrusion-color": "#aaa",
              "fill-extrusion-height": [
                "interpolate",
                ["linear"],
                ["zoom"],
                15,
                0,
                15.05,
                ["get", "height"],
              ],
              "fill-extrusion-base": [
                "interpolate",
                ["linear"],
                ["zoom"],
                15,
                0,
                15.05,
                ["get", "min_height"],
              ],
              "fill-extrusion-opacity": 0.6,
            },
          });
          // 显示点
          this.flyTo();
        }
      });
      // 单点标记
      this.mapbox.on("click", (e) => {
        if (this.preview) {
          return;
        }
        if (this.marker) {
          this.marker.remove();
        }
        this.marker = new mapboxgl.Marker({
          offset: [0, -25],
          draggable: this.draggable,
        })
          .setLngLat([e.lngLat.lng, e.lngLat.lat])
          .addTo(this.mapbox);
        if (this.decimal < 0) {
          this.decimal = 0;
        }
        if (this.decimal > 14) {
          this.decimal = 14;
        }
        let lng = e.lngLat.lng.toFixed(this.decimal);
        let lat = e.lngLat.lat.toFixed(this.decimal);
        this.currentValue = lng + ", " + lat;
        this.$emit("on-click", this.currentValue);
      });
    },
    flyTo() {
      if (this.data) {
        try {
          let lngLat = this.data.split(",");
          let lng = lngLat[0].trim();
          let lat = lngLat[1].trim();
          this.currentValue = this.data;
          if (this.marker) {
            this.marker.remove();
          }
          this.marker = new mapboxgl.Marker({
            offset: [0, -25],
            draggable: true,
          })
            .setLngLat([lng, lat])
            .addTo(this.mapbox);
          this.mapbox.flyTo({
            center: [lng, lat],
            essential: true,
          });
        } catch (error) {
          this.$Message.error("您输入的坐标不合法");
        }
      } else {
        this.currentValue = "";
        if (this.marker) {
          this.marker.remove();
        }
        this.mapbox.flyTo({
          center: this.center,
          zoom: 9,
          essential: true,
        });
      }
    },
    handleChange(v) {
      this.$emit("input", this.data);
      this.$emit("on-change", this.data);
    },
    setData(value) {
      if (!this.mapbox) {
        this.init();
      }
      if (value != this.data) {
        this.data = value;
        this.$emit("input", this.data);
        this.$emit("on-change", this.data);
      }
    },
    handleFull() {
      this.full = !this.full;
      if (this.full) {
        this.modalHeight = "100%";
      } else {
        this.modalHeight = "500px";
      }
      setTimeout(() => {
        this.mapbox.resize();
      }, 10);
    },
    handleShow() {
      if (!config.mapboxToken) {
        this.$Message.warning("请先配置Mapbox地图的accessToken");
        return;
      }
      this.showModal = true;
    },
    changeModal(v) {
      if (v) {
        setTimeout(() => {
          this.mapbox.resize();
          this.flyTo();
        }, 10);
      }
    },
    handelSubmit() {
      if (!this.currentValue) {
        this.$Message.warning("请在地图中点击鼠标选择一个地点");
        return;
      }
      this.data = this.currentValue;
      this.$emit("input", this.data);
      this.$emit("on-change", this.data);
      this.showModal = false;
    },
  },
  beforeDestroy() {
    // 调用销毁 API 对当前编辑器实例进行销毁
    if (this.mapbox != null) {
      this.mapbox.remove();
    }
  },
  watch: {
    value(val) {
      this.setData(val);
    },
  },
  mounted() {
    this.init();
  },
};
</script>
<style lang="less">
.mapboxgl-style-list {
  display: none;
}

.mapboxgl-ctrl-group .mapboxgl-style-list button {
  background: none;
  border: none;
  cursor: pointer;
  display: block;
  font-size: 14px;
  padding: 8px 8px 6px;
  text-align: right;
  width: 100%;
  height: auto;
}

.mapboxgl-style-list button.active {
  font-weight: bold;
}

.mapboxgl-style-list button:hover {
  background-color: rgba(0, 0, 0, 0.05);
}

.mapboxgl-style-list button + button {
  border-top: 1px solid #ddd;
}

.mapboxgl-style-switcher {
  background-image: url();
  background-position: center;
  background-repeat: no-repeat;
  background-size: 70%;
}
.map-modal {
  .ivu-modal-body {
    padding: 0px;
  }
}
</style>
